package de.koller.aframe.util.noise.n3D;

import de.koller.aframe.util.math.Function3DPreview;
import de.koller.aframe.util.misc.BufferedRandomFloat3D;
import de.koller.aframe.util.noise.n2D.Perlin2D;
import java.util.Random;

public class Perlin3DOctTree extends Noise3D {
	private final BufferedRandomFloat3D crnd;
	public final int posX, posY, posZ;
	private final int step;
	
	private float valueMultiplier = 1;
	private float persistence = .5f;
	private int steps = 0;
	
	private Perlin3DOctTree[][][] subTree = new Perlin3DOctTree[2][2][2];
	
	public static Perlin3DOctTree create( BufferedRandomFloat3D crnd, 
			int x, int y, int z, float persistence, int steps ) {
		Perlin3DOctTree qt = new Perlin3DOctTree( crnd, x, y, z );
		qt.createSubTree( persistence, steps );
		return qt;
	}
	
	public Perlin3DOctTree( BufferedRandomFloat3D crnd, int posX, int posY, int posZ ) {
		this( crnd, posX, posY, posZ, 0 );
	}
	
	public Perlin3DOctTree( BufferedRandomFloat3D crnd, int posX, int posY, int posZ, int step ) {
		super( new float[4][4][4] );
		
		this.step = step;
		this.posX = posX; this.posY = posY; this.posZ = posZ;
		this.crnd = crnd;
		
		for( int x = 0; x < 4; x++ )
			for( int y = 0; y < 4; y++ )
				for( int z = 0; z < 4; z++ )
					noise[x][y][z] = crnd.get( posX + x - 1, posY + y - 1, posZ + z - 1, step, 0 );
	}

	public void createSubTree( float persistence ) {
		createSubTree( persistence, -1 );
	}
	
	public void createSubTree( float persistence, int maxStep ) {
		this.steps = maxStep;
		this.persistence = persistence;
	}
	
	@Override
	public float getValue( float x, float y, float z ) {
		float f = super.getValue( x, y, z );
		float v = 0;
		
		if( steps > step ) {
			float dx = 2f * x;
			float dy = 2f * y;
			float dz = 2f * z;

			int ax = (int)Math.floor( dx );
			int ay = (int)Math.floor( dy );
			int az = (int)Math.floor( dz );
			dx %= 1f;
			dy %= 1f;
			dz %= 1f;

			Perlin3DOctTree qt = subTree[ax][ay][az];
			if( qt == null ) {
				subTree[ax][ay][az] = new Perlin3DOctTree( crnd, posX*2+ax, posY*2+ay, posZ*2+az, step + 1 );
				subTree[ax][ay][az].persistence = persistence;
				subTree[ax][ay][az].steps = steps;
				qt = subTree[ax][ay][az];
			}
			v = qt.getValue( dx, dy, dz );
		}
			
		return ( f + persistence*v ) * valueMultiplier;
	}
	
	@Override
	public void normalize( float height ) {
		throw new UnsupportedOperationException();
	}

	@Override
	public void normalizeMaxBound( float maxHeight ) {
		valueMultiplier = 1f / maxHeight;
	}
		
	public static void main( String[] args ) {
		Function3DPreview.PRINT_PROGRESS = true;
		Function3DPreview.Z_STEPS = 20;
		BufferedRandomFloat3D bfr = new BufferedRandomFloat3D( "./temp/cNoiseTest", new Random() );
		Noise3D n = Perlin3DOctTree.create( bfr, 0, 0, 0, .8f, 7 );
		n.normalizeMaxBound( Perlin2D.maxHeight( 0, 7, .8f ) );
		Function3DPreview.showFunction( 0, 0, 0, 1, 1, 1, 0, 1, n );
	}

}
